import copy
import json

from flask import current_app as app

from . import BaseHandler
from app.celery.handle_result.utils import convert_to_camel, get_warning_id
from app.libs.enums import TaskType, NetWorkType
from app.libs.display import NODE_AREA_NAME_MAP_REVERSE, NODE_AREA_NAME_MAP


ruleCheckers = [
    {
        "ruleId": 'respTime',
        "field": 'totalTime',
        "level": 2,
        "abnormalChecker": lambda item: item["value"] >= item["rule"] or item["value"] <= 0,
        "titleBuilder": lambda item: f"服务器响应时间超过{item['rule']}ms",
    },
    {
        "ruleId": 'lossRate',
        "field": 'lossRate',
        "level": 2,
        "abnormalChecker": lambda item: item["value"] >= item["rule"],
        "titleBuilder": lambda item: f"服务器丢包率超过{format(item['rule'], '.0%')}",
    },
    {
        "ruleId": 'statusCodeRange',
        "field": 'statusCode',
        "level": 2,
        "abnormalChecker": lambda item: any(map(lambda x: int(x[0]) <= item["value"] <= int(x[1]) if isinstance(x, list) else int(x) == item["value"], item["rule"])),
        "titleBuilder": lambda item: f'服务器响应状态码异常[{" ".join(map(lambda x: f"{x[0]}-{x[1]}" if isinstance(x, list) else x, item["rule"]))}]',
    },

]


class AvailableHandler(BaseHandler):

    def handle(self):
        return self._handle_available()

    def _handle_available(self):
        if len(self.response.get("detail", [])) == 0 and len(self.response.get("detail_v6", [])) == 0:
            app.logger.error(f"可用性任务{self.task.id}结果详情为空， 任务处理状态设置为失败：{self.task.result.addition}")
            self.task.status = "failed"
        area = self.task_settings.get("collect", {}).get("area", [])
        area_ipv6 = self.task_settings.get('collect', {}).get('area_ipv6', [])
        alert = self.task_settings.get("alert", {})
        rules = {
            "respTime": alert.get("respTime"),
            # "lossRate": alert.get("lossRate"),
            "statusCodeRange": alert.get("statusCodeRange")
        }
        self._available_response_scheme(area, rules)
        self._available_response_scheme(area_ipv6, rules, network_type=NetWorkType.v6.value)
        self._merge_response()
        warnings_addition = self._available_warning_check(target=self.target, response=self.response, rules=rules)
        abnormal_nodes = list()
        for warning in warnings_addition["warnings"]:
            abnormal_nodes.extend(warning.get("detail").get("area", []))
        abnormal_nodes = set(abnormal_nodes)

        node_abnormal_count = len(abnormal_nodes)
        node_total_count = len(area) + len(area_ipv6)
        warnings_addition["addition"]["nodeTotalCount"] = node_total_count
        warnings_addition["addition"]["nodeAbnormalCount"] = node_abnormal_count
        return warnings_addition

    def _available_response_scheme(self, area, rules, network_type=NetWorkType.v4.value):
        """格式化response数据字段"""
        response = self.response
        resp_avg_key = 'resp_avg'
        detail_key = 'detail'
        network_display_name = 'IPv4'
        if network_type == NetWorkType.v6.value:
            resp_avg_key = f'{resp_avg_key}_v6'
            detail_key = f'{detail_key}_v6'
            network_display_name = 'IPv6'
        resp_avg = response.pop(resp_avg_key, 0)
        response[convert_to_camel(resp_avg_key)] = resp_avg if resp_avg else rules.get('respTime', 0)
        if not response.get(detail_key):
            response[detail_key] = []
        exists_nodes = set()
        new_detail = []
        for detail_item in response.get(detail_key):
            item_area = detail_item.pop("area", "")
            if (item_node := NODE_AREA_NAME_MAP_REVERSE.get(item_area)) not in area:
                continue
            resp_time = detail_item.pop("resp_time", 0)
            response_time = detail_item.pop("response_time", 0)

            detail_item["ip"] = detail_item.pop("ip", "")
            detail_item["area"] = item_area
            detail_item["totalTime"] = resp_time if resp_time else rules.get('respTime', 0)
            detail_item["contentLength"] = detail_item.pop("content_len", 0)
            detail_item['network_display_name'] = network_display_name

            if self.task_type == TaskType.http.value:
                detail_item["respTime"] = response_time if response_time else rules.get('respTime', 0)
            detail_item["node"] = item_node
            detail_item["response"] = "\n".join(
                [f"{key}: {value.encode('utf-8', 'replace').decode('utf-8')}" for key, value in json.loads(detail_item.pop("header", None) or "{}").items()])

            exists_nodes.add(detail_item["node"])

            not_camel_field = {"has_error", "inner_type", "match_key"}
            for key_, value in copy.copy(detail_item).items():
                if isinstance(value, float):
                    detail_item[key_] = round(value, 2)
                if key_ not in not_camel_field:
                    detail_item[convert_to_camel(key_)] = detail_item.pop(key_)
            detail_item.pop("instance", None)
            new_detail.append(detail_item)
        for node in area:
            if node not in exists_nodes:
                response[detail_key].append({
                    "ip": '--',
                    "lossRate": 100,
                    "area": NODE_AREA_NAME_MAP.get(node, node),
                    "totalTime": 0,
                    "node": node,
                    "response": '',
                    'network_display_name': network_display_name
                })
        response[detail_key] = new_detail
        if not new_detail:
            response[resp_avg_key] = 0

        return response

    def _merge_response(self):
        new_detail = []
        resp_avg = 0
        avg = 0
        if self.response['detail']:
            new_detail.extend(self.response['detail'])
            resp_avg += self.response['respAvg']
            avg += 1
        if self.response['detail_v6']:
            new_detail.extend(self.response['detail_v6'])
            resp_avg += self.response['respAvgV6']
            avg += 1
        if not avg:
            avg = 1
        self.response['respAvg'] = round(resp_avg / avg, 2)
        self.response['detail'] = new_detail

    def _available_warning_check(self, target, response, rules):
        detail = response.get("detail", [])
        node_detail_map = {}
        for item in detail:
            node = item["node"]
            net_work_type = item.get('networkDisplayName', NetWorkType.v4.value)
            item.update({
                "warnings": [],
                "securityStatus": "safe"
            })
            node_detail_map[f"{node}_{net_work_type}"] = item

        warning_map = {}
        for checker in ruleCheckers:
            rule_id = checker["ruleId"]
            filed = checker["field"]
            level = checker["level"]
            abnormal_checker_func = checker["abnormalChecker"]
            title_builder_func = checker["titleBuilder"]
            rule = rules.get(rule_id)

            if not rule:
                continue
            event_id = get_warning_id(
                task_type="http",
                job_id=self.task.jobId,
                url=target,
                risk_title=rule_id,
            )
            abnormal_node = []
            abnormal_area = []
            for result in detail:
                value = result.get(filed, 0)
                checker_bool = abnormal_checker_func({"value": value, "rule": rule})
                if checker_bool:
                    result_node = f"{result['node']}_{result.get('networkDisplayName', NetWorkType.v4.value)}"
                    node_detail_map[result_node]["warnings"].append(event_id)
                    node_detail_map[result_node]["securityStatus"] = 'warning'
                    abnormal_node.append(result["node"])
                    abnormal_area.append(result_node)
            if abnormal_node:
                warning_map[event_id] = {
                    "level": level,
                    "id": event_id,
                    "title": title_builder_func({"rule": rule}),
                    "detail": {
                        "node": abnormal_node,
                        "area": abnormal_area
                    }
                }
        warnings_addition = {
            "addition": {
                "respAvg": response.get("respAvg"),
                "detail": list(node_detail_map.values()),
            },
            "warnings": list(warning_map.values())
        }
        return warnings_addition
